package com.zhl.common.config;

import cn.hutool.core.util.StrUtil;
import com.zhl.common.annotation.ExceptionCode;
import com.zhl.common.enums.BaseResultCode;
import com.zhl.common.enums.ResultCode;
import com.zhl.common.exception.APIException;
import com.zhl.common.exception.BizException;
import com.zhl.common.request.entity.RequestDetail;
import com.zhl.common.request.holder.RequestDetailThreadLocal;
import com.zhl.common.vo.R;
import com.zhl.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Field;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.zhl.common.enums.ResultCode.METHOD_NOT_ALLOWED;
import static com.zhl.common.enums.ResultCode.REQUIRED_FILE_PARAM_EX;

/**
 * 全局异常处理器
 * 
 * @author zhl
 * @date 2019
 */
@RestControllerAdvice
@Slf4j
public class GobleExceptionHandler {

	/**
	 * 处理自定义异常
	 */
	@ExceptionHandler(GobleException.class)
	public Result<?> handleRRException(GobleException e){
		printRequestDetail();
		printApiCodeException(ResultCode.INTERNAL_SERVER_ERROR, e);
		return Result.error(e.getMessage());
	}

	// 处理自定义异常
	@ExceptionHandler(APIException.class)
	public Result<String> APIExceptionHandler(APIException e) {
		return new Result<>(ResultCode.FAILED, e.getMsg());
	}

	// 404异常处理
	@ExceptionHandler(NoHandlerFoundException.class)
	public Result<?> handlerNoFoundException(NoHandlerFoundException e) {
		log.error(e.getMessage(), e);
		return Result.error(404, "路径不存在，请检查路径是否正确");
	}

	@ExceptionHandler(DuplicateKeyException.class)
	public Result<?> handleDuplicateKeyException(DuplicateKeyException e){
		log.error(e.getMessage(), e);
		return Result.error("数据库中已存在该记录");
	}

	@ExceptionHandler(AccessDeniedException.class)
	public Result<?> handleAuthorizationException(AccessDeniedException e){
		log.error(e.getMessage(), e);
		return Result.error("没有权限，请联系管理员授权");
	}

	//  非法获取异常
	@ExceptionHandler(IllegalAccessException.class)
	public Result<?> handleIllegalAccessException(IllegalAccessException e){
		log.error(e.getMessage(), e);
		return Result.error(e.getMessage());
	}

	//spring默认上传大小100MB 超出大小捕获异常MaxUploadSizeExceededException
	@ExceptionHandler(MaxUploadSizeExceededException.class)
	public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e){
		log.error(e.getMessage(), e);
		return Result.error(e.getMessage());
	}

	//处理字段太长异常
//	@ExceptionHandler(DataIntegrityViolationException.class)
//	public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e){
//		log.error(e.getMessage(), e);
//		return Result.error(e.getMessage());
//	}


	//简版
//	@ExceptionHandler(MethodArgumentNotValidException.class)
//	public Result<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
//		// 从异常对象中拿到ObjectError对象
//		ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
//		// 然后提取错误提示信息进行返回
//		return new Result<>(ResultCode.VALIDATE_FAILED, objectError.getDefaultMessage());
//	}

	//升级版
	//就只需要在入参的成员变量上加上Validator校验规则注解，
	// 然后在参数上加上@Valid注解即可完成校验，校验失败会自动返回错误提示信息，无需任何其他代码
	//Validator + 自动抛出异常、我们完全可以将BindingResult这一步给去掉，去掉BindingResult后会自动引发异常
	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public Result<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) throws NoSuchFieldException {
		// 从异常对象中拿到错误信息
		String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();

		// 参数的Class对象，等下好通过字段名称获取Field对象
		Class<?> parameterType = e.getParameter().getParameterType();
		// 拿到错误的字段名称
		String fieldName = e.getBindingResult().getFieldError().getField();
		Field field = parameterType.getDeclaredField(fieldName);
		// 获取Field对象上的自定义注解
		ExceptionCode annotation = field.getAnnotation(ExceptionCode.class);
		// 有注解的话就返回注解的响应信息
		if (annotation != null) {
			return new Result<>(annotation.value(),annotation.message(),defaultMessage);
		}
		// 没有注解就提取错误提示信息进行返回统一错误码
		return new Result<>(ResultCode.VALIDATE_FAILED, defaultMessage);
	}

	/**
	 * 获取httpStatus格式化字符串
	 *
	 * @param responseEnum
	 * @return
	 */
	private String getApiCodeString(BaseResultCode responseEnum) {
		if (responseEnum != null) {
			return String.format("errorCode: %s, errorMessage: %s", responseEnum.getCode(), responseEnum.getMsg());
		}
		return null;
	}

	/**
	 * 打印请求详情
	 */
	private void printRequestDetail() {
		RequestDetail requestDetail = RequestDetailThreadLocal.getRequestDetail();
		if (requestDetail != null) {
			log.error("异常来源：ip: {}, path: {}", requestDetail.getIp(), requestDetail.getPath());
		}
	}
	/**
	 * 打印错误码及异常
	 *
	 * @param responseEnum
	 * @param exception
	 */
	private void printApiCodeException(BaseResultCode responseEnum, Exception exception) {
		log.error(getApiCodeString(responseEnum), exception);
	}

	@ExceptionHandler(BizException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R bizException(BizException ex, HttpServletRequest request) {
		log.warn("BizException:", ex);
		return R.result(ex.getCode(), null, ex.getMessage()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(HttpMessageNotReadableException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R httpMessageNotReadableException(HttpMessageNotReadableException ex, HttpServletRequest request) {
		log.warn("HttpMessageNotReadableException:", ex);
		String message = ex.getMessage();
		if (StrUtil.containsAny(message, "Could not read document:")) {
			String msg = String.format("无法正确的解析json类型的参数：%s", StrUtil.subBetween(message, "Could not read document:", " at "));
			return R.result(ResultCode.PARAM_EX.getCode(), null, msg).setPath(request.getRequestURI());
		}
		return R.result(ResultCode.PARAM_EX.getCode(), null, ResultCode.PARAM_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(BindException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R bindException(BindException ex, HttpServletRequest request) {
		log.warn("BindException:", ex);
		try {
			String msgs = ex.getBindingResult().getFieldError().getDefaultMessage();
			if (StrUtil.isNotEmpty(msgs)) {
				return R.result(ResultCode.PARAM_EX.getCode(), null, msgs).setPath(request.getRequestURI());
			}
		} catch (Exception ee) {
		}
		StringBuilder msg = new StringBuilder();
		List<FieldError> fieldErrors = ex.getFieldErrors();
		fieldErrors.forEach((oe) ->
				msg.append("参数:[").append(oe.getObjectName())
						.append(".").append(oe.getField())
						.append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
		);
		return R.result(ResultCode.PARAM_EX.getCode(), null, msg.toString()).setPath(request.getRequestURI());
	}


	@ExceptionHandler(MethodArgumentTypeMismatchException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex, HttpServletRequest request) {
		log.warn("MethodArgumentTypeMismatchException:", ex);
		MethodArgumentTypeMismatchException eee = (MethodArgumentTypeMismatchException) ex;
		StringBuilder msg = new StringBuilder("参数：[").append(eee.getName())
				.append("]的传入值：[").append(eee.getValue())
				.append("]与预期的字段类型：[").append(eee.getRequiredType().getName()).append("]不匹配");
		return R.result(ResultCode.PARAM_EX.getCode(), null, msg.toString()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(IllegalStateException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R illegalStateException(IllegalStateException ex, HttpServletRequest request) {
		log.warn("IllegalStateException:", ex);
		return R.result(ResultCode.ILLEGALA_ARGUMENT_EX.getCode(), null, ResultCode.ILLEGALA_ARGUMENT_EX.getMsg()).setPath(request.getRequestURI());
	}

	// 缺少请求参数异常处理
	@ExceptionHandler(MissingServletRequestParameterException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R missingServletRequestParameterException(MissingServletRequestParameterException ex, HttpServletRequest request) {
		log.warn("MissingServletRequestParameterException:", ex);
		StringBuilder msg = new StringBuilder();
		msg.append("缺少必须的[").append(ex.getParameterType()).append("]类型的参数[").append(ex.getParameterName()).append("]");
		return R.result(ResultCode.ILLEGALA_ARGUMENT_EX.getCode(), null, msg.toString()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(NullPointerException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R nullPointerException(NullPointerException ex, HttpServletRequest request) {
		log.warn("NullPointerException:", ex);
		return R.result(ResultCode.NULL_POINT_EX.getCode(), null, ResultCode.NULL_POINT_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R illegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) {
		log.warn("IllegalArgumentException:", ex);
		return R.result(ResultCode.ILLEGALA_ARGUMENT_EX.getCode(), null, ResultCode.ILLEGALA_ARGUMENT_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) {
		log.warn("HttpMediaTypeNotSupportedException:", ex);
		MediaType contentType = ex.getContentType();
		if (contentType != null) {
			StringBuilder msg = new StringBuilder();
			msg.append("请求类型(Content-Type)[").append(contentType.toString()).append("] 与实际接口的请求类型不匹配");
			return R.result(ResultCode.MEDIA_TYPE_EX.getCode(), null, msg.toString()).setPath(request.getRequestURI());
		}
		return R.result(ResultCode.MEDIA_TYPE_EX.getCode(), null, "无效的Content-Type类型").setPath(request.getRequestURI());
	}

	@ExceptionHandler(MissingServletRequestPartException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R missingServletRequestPartException(MissingServletRequestPartException ex, HttpServletRequest request) {
		log.warn("MissingServletRequestPartException:", ex);
		return R.result(REQUIRED_FILE_PARAM_EX.getCode(), null, REQUIRED_FILE_PARAM_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(ServletException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R servletException(ServletException ex, HttpServletRequest request) {
		log.warn("ServletException:", ex);
		String msg = "UT010016: Not a multi part request";
		if (msg.equalsIgnoreCase(ex.getMessage())) {
			return R.result(REQUIRED_FILE_PARAM_EX.getCode(), null, REQUIRED_FILE_PARAM_EX.getMsg());
		}
		return R.result(ResultCode.SYSTEM_BUSY.getCode(), null, ex.getMessage()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(MultipartException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R multipartException(MultipartException ex, HttpServletRequest request) {
		log.warn("MultipartException:", ex);
		return R.result(REQUIRED_FILE_PARAM_EX.getCode(), null, REQUIRED_FILE_PARAM_EX.getMsg()).setPath(request.getRequestURI());
	}

	/**
	 * jsr 规范中的验证异常
	 *
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
		log.warn("ConstraintViolationException:", ex);
		Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
		String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));

		ConstraintViolation<?> violation = violations.iterator().next();
		String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
		String message2 = String.format("%s:%s", path, violation.getMessage());

		return R.result(ResultCode.BASE_VALID_PARAM.getCode(), null, message).setPath(request.getRequestURI());
	}

	/**
	 * spring 封装的参数验证异常， 在conttoller中没有写result参数时，会进入
	 *
	 * @param ex
	 * @return
	 */
//	@ExceptionHandler(MethodArgumentNotValidException.class)
//	@ResponseStatus(HttpStatus.BAD_REQUEST)
//	public R methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
//		log.warn("MethodArgumentNotValidException:", ex);
//		return R.result(ResultCode.BASE_VALID_PARAM.getCode(), null, ex.getBindingResult().getFieldError().getDefaultMessage()).setPath(request.getRequestURI());
//	}

	/**
	 * 其他异常
	 *
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(Exception.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R otherExceptionHandler(Exception ex, HttpServletRequest request) {
		log.warn("Exception:", ex);
		if (ex.getCause() instanceof BizException) {
			return this.bizException((BizException) ex.getCause(), request);
		}
		return R.result(ResultCode.SYSTEM_BUSY.getCode(), null, ResultCode.SYSTEM_BUSY.getMsg()).setPath(request.getRequestURI());
	}


	/**
	 * 返回状态码:405
	 * 不支持的方法异常
	 */
	@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
		log.warn("HttpRequestMethodNotSupportedException:", ex);
		log.info("请求方法为："+ex.getMethod());
		return R.result(METHOD_NOT_ALLOWED.getCode(), null, METHOD_NOT_ALLOWED.getMsg()).setPath(request.getRequestURI());
	}


	@ExceptionHandler(PersistenceException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R persistenceException(PersistenceException ex, HttpServletRequest request) {
		log.warn("PersistenceException:", ex);
		if (ex.getCause() instanceof BizException) {
			BizException cause = (BizException) ex.getCause();
			return R.result(cause.getCode(), null, cause.getMessage());
		}
		return R.result(ResultCode.SQL_EX.getCode(), null, ResultCode.SQL_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(MyBatisSystemException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R myBatisSystemException(MyBatisSystemException ex, HttpServletRequest request) {
		log.warn("PersistenceException:", ex);
		if (ex.getCause() instanceof PersistenceException) {
			return this.persistenceException((PersistenceException) ex.getCause(), request);
		}
		return R.result(ResultCode.SQL_EX.getCode(), null, ResultCode.SQL_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(SQLException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R sqlException(SQLException ex, HttpServletRequest request) {
		log.warn("SQLException:", ex);
		return R.result(ResultCode.SQL_EX.getCode(), null, ResultCode.SQL_EX.getMsg()).setPath(request.getRequestURI());
	}

	@ExceptionHandler(DataIntegrityViolationException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public R dataIntegrityViolationException(DataIntegrityViolationException ex, HttpServletRequest request) {
		log.warn("DataIntegrityViolationException:", ex);
		return R.result(ResultCode.SQL_EX.getCode(), null, ResultCode.SQL_EX.getMsg()).setPath(request.getRequestURI());
	}
}
